
import std.random;
import std.math;
import Neuron;

float M_PI = 3.14159265358979323846;
float psiG, chiG;

class BackPropagationNetwork{
	Neuron x, y;
	Neuron[] hiddenLayer1;
	Neuron[] hiddenLayer2;
	Neuron psi, chi;

	this(int hiddenSize1, int hiddenSize2){
		hiddenLayer1 = new Neuron[hiddenSize1];
		hiddenLayer2 = new Neuron[hiddenSize2];
	
		x = new Neuron(1);
		y = new Neuron(1);
	
		for(int i = 0; i < hiddenSize1; ++i)
			hiddenLayer1[i] = new Neuron(2);
	
		for(int i = 0; i < hiddenSize2; ++i)
			hiddenLayer2[i] = new Neuron(hiddenSize1);
	
		psi = new Neuron(hiddenSize2);
		chi = new Neuron(hiddenSize2);
	}

	void eval(float xIn, float yIn, inout float psiG, inout float chiG){
		assert(xIn >= 0 && xIn <= 1);
		assert(yIn >= 0 && yIn <= 1);

		for(int j = 0; j < hiddenLayer1.length; ++j){
			hiddenLayer1[j].inputs[0] = xIn;
			hiddenLayer1[j].inputs[1] = yIn;
			hiddenLayer1[j].eval();
		}
		// hiddenLayer1 -> hiddenLayer2
		for(int j = 0; j < hiddenLayer2.length; ++j){
			for(int k = 0; k < hiddenLayer1.length; ++k)
				hiddenLayer2[j].inputs[k] = hiddenLayer1[k].output;
			hiddenLayer2[j].eval();
		}
		// hiddenLayer2 -> output
		for(int j = 0; j < hiddenLayer2.length; ++j){
			psi.inputs[j] = hiddenLayer2[j].output;
			chi.inputs[j] = hiddenLayer2[j].output;
		}
		psiG = psi.eval();
		chiG = chi.eval();
	}

	void train(int steps, float l1, float l2){
		float ro = 0.5;
		float psiOut, chiOut;
		
		for(int i = 0; i < steps; ++i){
			ro = -log(cast(float)(i+1)/cast(float)(steps+1));//jakos lepiej wychodzi z taka "stala" uczenia
//			writefln(ro);
			assert(ro > 0);
			{
				psiOut = cast(float)((rand() % 10000) / 10000.0);
				chiOut = cast(float)((rand() % 10000) / 10000.0);
			
				{
					float psi = 2. * cast(float)M_PI * psiOut;
					float eX = (l1*sin(psi));
					float eY = (l1*cos(psi));

					float chi = cast(float)M_PI * chiOut;
					float eX1f = (l2*sin(chi));
					float eY1f = (l2*cos(chi));
					float eX1 = (eX1f*cos(psi) + eY1f*sin(psi));
					float eY1 = (-eX1f*sin(psi) + eY1f*cos(psi));
					float xO = eX + eX1;
					float yO = eY + eY1;
					x.output = (xO + l1 + l2)/ (2*(l1+l2));
//					writefln(x.output);
					y.output = (yO + l1 + l2)/ (2*(l1+l2));
//					writefln(y.output);
				}
			}
			// input -> hiddenLayer1
			for(int j = 0; j < hiddenLayer1.length; ++j){
				hiddenLayer1[j].inputs[0] = x.output;
				hiddenLayer1[j].inputs[1] = y.output;
				hiddenLayer1[j].eval();
			}
			// hiddenLayer1 -> hiddenLayer2
			for(int j = 0; j < hiddenLayer2.length; ++j){
				for(int k = 0; k < hiddenLayer1.length; ++k)
					hiddenLayer2[j].inputs[k] = hiddenLayer1[k].output;
				hiddenLayer2[j].eval();
			}
			// hiddenLayer2 -> output
			for(int j = 0; j < hiddenLayer2.length; ++j){
				psi.inputs[j] = hiddenLayer2[j].output;
				chi.inputs[j] = hiddenLayer2[j].output;
			}
			psi.eval();
			chi.eval();

			// output errors
			chi.errorFactor = chiOut - chi.output;
			chi.delta = chi.output * (1 - chi.output) * chi.errorFactor;
			psi.errorFactor = psiOut - psi.output;
			psi.delta = psi.output * (1 - psi.output) * psi.errorFactor;
			// hiddenLayer2 errors
			for(int j = 0; j < hiddenLayer2.length; ++j){
				hiddenLayer2[j].errorFactor = chi.delta * chi.weights[j] + psi.delta * psi.weights[j];
				hiddenLayer2[j].delta = hiddenLayer2[j].output * (1 - hiddenLayer2[j].output) * hiddenLayer2[j].errorFactor;
			}
			// hiddenLayer1 errors
			for(int j = 0; j < hiddenLayer1.length; ++j){
				hiddenLayer1[j].errorFactor = 0;
				for(int k = 0; k < hiddenLayer2.length; ++k)
					hiddenLayer1[j].errorFactor += hiddenLayer2[k].weights[j] * hiddenLayer2[k].delta;
			}
			for(int j = 0; j < hiddenLayer1.length; ++j)
				hiddenLayer1[j].delta = hiddenLayer1[j].output * (1 - hiddenLayer1[j].output) * hiddenLayer1[j].errorFactor;
			
			// update weights
			for(int j = 0; j < hiddenLayer1.length; ++j){
				hiddenLayer1[j].weights[0] += ro * x.output * hiddenLayer1[j].delta;
				hiddenLayer1[j].weights[1] += ro * y.output * hiddenLayer1[j].delta;
			}
			for(int j = 0; j < hiddenLayer2.length; ++j){
				for(int k = 0; k < hiddenLayer1.length; ++k)
					hiddenLayer2[j].weights[k] += ro * hiddenLayer1[k].output * hiddenLayer2[j].delta;
			}
			for(int j = 0; j < hiddenLayer2.length; ++j){
				chi.weights[j] += ro * hiddenLayer2[j].output * chi.delta;
				psi.weights[j] += ro * hiddenLayer2[j].output * psi.delta;
			}			
		}
	}
}

